package com.hcl.security.config;

import java.util.List;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import com.hcl.security.config.username.UsernameAuthenticationProvider;
import com.hcl.security.config.username.UsernameFilter;
import com.hcl.security.filter.ValidateTokenFilter;
import com.hcl.security.handle.*;

/**
 * Sercurity配置类
 */
@Configuration
@EnableWebSecurity // 开启SpringSecurity
@EnableMethodSecurity // 开启方法级别的安全注解，如@PreAuthorize
public class SercurityConfig {

	/**
	 * 认证管理器
	 *
	 * @return
	 */
	@Bean
	public ProviderManager providerManager() {
		return new ProviderManager(
				List.of(new UsernameAuthenticationProvider()));
	}

	/**
	 * 登出成功处理器
	 *
	 * @return
	 */

	@Bean
	public LogoutSuccessHandlerImpl logoutSuccessHandler() {
		return new LogoutSuccessHandlerImpl();
	}

	/**
	 * 无权限处理器
	 *
	 * @return
	 */
	@Bean
	public AccessDeniedHandler accessDeniedHandler() {
		return new AccessDeniedHandlerImpl();
	}

	/**
	 * 登录成功处理器
	 */
	@Bean
	public LoginAuthenticationSuccessHandler loginAuthenticationSuccessHandler() {
		return new LoginAuthenticationSuccessHandler();
	}

	/**
	 * 登录失败处理器
	 *
	 * @return
	 */
	@Bean
	public LoginAuthenticationFailureHandler loginAuthenticationFailureHandler() {
		return new LoginAuthenticationFailureHandler();
	}

	/**
	 * 用户登录过滤器
	 *
	 * @return
	 */
	@Bean
	public UsernameFilter usernameFilter() {
		return new UsernameFilter(new AntPathRequestMatcher("/login", "POST"),
				providerManager(), loginAuthenticationSuccessHandler(),
				loginAuthenticationFailureHandler());
	}

	/**
	 * 认证失败处理器
	 */
	@Bean
	public AuthenticationEntryPointImpl authenticationEntryPoint() {
		return new AuthenticationEntryPointImpl();
	}

	/**
	 * 验证token过滤器
	 *
	 * @return
	 */
	@Bean
	public ValidateTokenFilter validateTokenFilter() {
		return new ValidateTokenFilter();
	}

	/**
	 * 密码加密器
	 *
	 * @return
	 */
	@Bean
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}

	/**
	 * 配置登录过滤器
	 *
	 * @param http
	 * @return
	 * @throws Exception
	 */
	@Bean
	public SecurityFilterChain loginFilterChain(HttpSecurity http)
			throws Exception {
		http.formLogin(AbstractHttpConfigurer::disable)
				.httpBasic(AbstractHttpConfigurer::disable)
				.logout(AbstractHttpConfigurer::disable)
				.sessionManagement(AbstractHttpConfigurer::disable)
				.csrf(AbstractHttpConfigurer::disable)
				// requestCache用于重定向，前后端分析项目无需重定向，requestCache也用不上
				.requestCache(AbstractHttpConfigurer::disable)
				// 无需给用户一个匿名身份
				.anonymous(AbstractHttpConfigurer::disable);

		// 处理 SpringSecurity 异常响应结果。响应数据的结构，改成业务统一的JSON结构。不要框架默认的响应结构
		http.exceptionHandling(exceptionHandling -> exceptionHandling
				// 认证失败异常
				.authenticationEntryPoint(authenticationEntryPoint())
				// 鉴权失败异常
				.accessDeniedHandler(accessDeniedHandler()));
		// 使用securityMatcher限定当前配置作用的路径
		http.securityMatcher("/**").authorizeHttpRequests(
				authorize -> authorize.anyRequest().authenticated());

		http.addFilterBefore(usernameFilter(),
				ExceptionTranslationFilter.class);
		http.addFilterBefore(validateTokenFilter(),
				ExceptionTranslationFilter.class);
		http.logout(logout -> logout.logoutUrl("/logout")
				.logoutSuccessHandler(logoutSuccessHandler()));
		return http.build();
	}

}
